Converted these images to PNG, saving a handful of bytes per image
[adiumx.git] / Plugins / Status Menu Item / CBStatusMenuItemController.m
blob86d84aec0c010756be22b251c719a5ae274cd344
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import <Adium/AIAccountControllerProtocol.h>
18 #import <Adium/AIChatControllerProtocol.h>
19 #import <Adium/AIInterfaceControllerProtocol.h>
20 #import <Adium/AIStatusControllerProtocol.h>
21 #import <Adium/AIContactControllerProtocol.h>
22 #import <Adium/AIListObject.h>
23 #import "CBStatusMenuItemPlugin.h"
24 #import "CBStatusMenuItemController.h"
25 #import "AIMenuBarIcons.h"
26 #import <AIUtilities/AIApplicationAdditions.h>
27 #import <AIUtilities/AIMenuAdditions.h>
28 #import <AIUtilities/AIEventAdditions.h>
29 #import <AIUtilities/AIArrayAdditions.h>
30 #import <AIUtilities/AIImageAdditions.h>
31 #import <Adium/AIAccount.h>
32 #import <Adium/AIChat.h>
33 #import <Adium/AIListContact.h>
34 #import <Adium/AIStatusIcons.h>
35 #import <Adium/AIStatusMenu.h>
36 #import <Adium/AIAccountMenu.h>
37 #import <Adium/AIContactMenu.h>
38 #import <AIUtilities/AIColorAdditions.h>
39 #import <AIUtilities/AIStringAdditions.h>
40 #import <Adium/AIPreferenceControllerProtocol.h>
41 // For the KEY_SHOW_OFFLINE_CONTACTS and PREF_GROUP_CONTACT_LIST_DISPLAY
42 #import "AIContactController.h"
44 #define STATUS_ITEM_MARGIN 8
46 @interface CBStatusMenuItemController (PRIVATE)
47 - (void)activateAdium:(id)sender;
48 - (void)setIconImage:(NSImage *)inImage;
49 - (NSImage *)badgeDuck:(NSImage *)duckImage withImage:(NSImage *)inImage;
50 - (void)updateMenuIcons;
51 - (void)updateMenuIconsBundle;
52 - (void)updateUnreadCount;
53 - (void)updateOpenChats;
54 @end
56 @implementation CBStatusMenuItemController
58 + (CBStatusMenuItemController *)statusMenuItemController
60         return [[[self alloc] init] autorelease];
63 - (id)init
65         if ((self = [super init])) {
66                 //Create and set up the status item
67                 statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
68                 [statusItem setHighlightMode:YES];
69                 
70                 unviewedContent = NO;
71                 [self updateMenuIconsBundle];
72                 
73                 // Create our menus
74                 mainMenu = [[NSMenu alloc] init];
75                 [mainMenu setDelegate:self];
77                 mainContactsMenu = [[NSMenu alloc] init];
78                 [mainContactsMenu setDelegate:self];
80                 mainAccountsMenu = [[NSMenu alloc] init];
81                 [mainAccountsMenu setDelegate:self];
83                 // Set the main menu as the status item's menu
84                 [statusItem setMenu:mainMenu];
85                 
86                 // Create the caches for our menu items
87                 accountMenuItemsArray = [[NSMutableArray alloc] init];
88                 stateMenuItemsArray = [[NSMutableArray alloc] init];
89                 openChatsArray = [[NSMutableArray alloc] init];
90                 contactMenuItemsArray = [[NSMutableArray alloc] init];
92                 // Flag all the menus as needing updates
93                 mainMenuNeedsUpdate = YES;
94                 contactsMenuNeedsUpdate = YES;
95                 accountsMenuNeedsUpdate = YES;
96                 
97                 NSNotificationCenter *notificationCenter = [adium notificationCenter];
98                 //Register to recieve chat opened and chat closed notifications
99                 [notificationCenter addObserver:self
100                                        selector:@selector(updateOpenChats)
101                                            name:Chat_DidOpen
102                                          object:nil];
103                 [notificationCenter addObserver:self
104                                        selector:@selector(updateOpenChats)
105                                            name:Chat_WillClose
106                                          object:nil];
107                 [notificationCenter addObserver:self
108                                        selector:@selector(updateOpenChats)
109                                            name:Chat_OrderDidChange
110                                          object:nil];           
111                 
112                 [notificationCenter addObserver:self
113                                                            selector:@selector(updateMenuIcons)
114                                                                    name:AIStatusIconSetDidChangeNotification
115                                                                  object:nil];
116                 
117                 // Register for our menu bar icon set changing
118                 [[adium notificationCenter] addObserver:self
119                                                                            selector:@selector(menuBarIconsDidChange:)
120                                                                                    name:AIMenuBarIconsDidChangeNotification
121                                                                                  object:nil];
122                 
123                 // Register as a chat observer so we can know the status of unread messages
124                 [[adium chatController] registerChatObserver:self];
125                 
126                 // Register as a list object observer so we can know when accounts need to show reconnecting
127             [[adium contactController] registerListObjectObserver:self];
128                 
129                 // Register as an observer of the preference group so we can update our "show offline contacts" option
130                 [[adium preferenceController] registerPreferenceObserver:self
131                                                                                                                 forGroup:PREF_GROUP_CONTACT_LIST_DISPLAY];
132                 
133                 // Register as an observer of our own preference group
134                 [[adium preferenceController] registerPreferenceObserver:self
135                                                                                                                 forGroup:PREF_GROUP_STATUS_MENU_ITEM];          
136                 
137                 //Register to recieve active state changed notifications
138                 [notificationCenter addObserver:self
139                                        selector:@selector(updateMenuIcons)
140                                            name:AIStatusActiveStateChangedNotification
141                                          object:nil];
142                 
143                 //Register ourself for the status menu items
144                 statusMenu = [[AIStatusMenu statusMenuWithDelegate:self] retain];
145                 
146                 //Account menu
147                 accountMenu = [[AIAccountMenu accountMenuWithDelegate:self
148                                                                                                   submenuType:AIAccountStatusSubmenu
149                                                                                            showTitleVerbs:YES] retain];
150                 
151                 //Contact menu
152                 contactMenu = [[AIContactMenu contactMenuWithDelegate:self
153                                                                                   forContactsInObject:nil] retain];
154         }
155         
156         return self;
159 - (void)dealloc
161         // Invalidate and release our timers
162         [self invalidateTimers];
163         
164         //Unregister ourself
165         [[adium contactController] unregisterListObjectObserver:self];
166         [[adium chatController] unregisterChatObserver:self];
167         [[adium notificationCenter] removeObserver:self];
168         [[adium preferenceController] unregisterPreferenceObserver:self];
169         
170         //Release our objects
171         [[statusItem statusBar] removeStatusItem:statusItem];
173         // All the temporary NSMutableArrays we store
174         [accountMenuItemsArray release];
175         [stateMenuItemsArray release];
176         [openChatsArray release];
177         [contactMenuItemsArray release];
178         
179         // The menus
180         [mainMenu release];
181         [mainContactsMenu release];
182         [mainAccountsMenu release];
183         
184         // Release our various menus.
185         [accountMenu setDelegate:nil]; [accountMenu release];
186         [contactMenu setDelegate:nil]; [contactMenu release];
187         [statusMenu setDelegate:nil]; [statusMenu release];
189         // Release our AIMenuBarIcons bundle
190         [menuIcons release];
191         
192         // Can't release this because it causes a crash on quit. rdar://4139755, rdar://4160625, and #743. --boredzo
193         // [statusItem release];
194         
195         //To the superclass, Robin!
196         [super dealloc];
199 //Icon State --------------------------------------------------------
200 #pragma mark Icon State
202 #define PREF_GROUP_APPEARANCE           @"Appearance"
203 #define KEY_MENU_BAR_ICONS                      @"Menu Bar Icons"
204 #define EXTENSION_MENU_BAR_ICONS        @"AdiumMenuBarIcons"
205 #define RESOURCE_MENU_BAR_ICONS         @"Menu Bar Icons"
207 - (void)updateMenuIconsBundle
209         NSString *menuIconPath = nil, *menuIconName;
210         
211         menuIconName = [[adium preferenceController] preferenceForKey:KEY_MENU_BAR_ICONS
212                                                                                                                         group:PREF_GROUP_APPEARANCE
213                                                                                                                    object:nil];
214         
215         // Get the path of the pack if found.
216         if (menuIconName) {
217                 menuIconPath = [adium pathOfPackWithName:menuIconName
218                                                                            extension:EXTENSION_MENU_BAR_ICONS
219                                                           resourceFolderName:RESOURCE_MENU_BAR_ICONS];
220         }
221         
222         // If the pack is not found, get the default one.
223         if (!menuIconPath || !menuIconName) {
224                 menuIconName = [[adium preferenceController] defaultPreferenceForKey:KEY_MENU_BAR_ICONS
225                                                                                                                                            group:PREF_GROUP_APPEARANCE
226                                                                                                                                           object:nil];                                                                                                                                    
227                 menuIconPath = [adium pathOfPackWithName:menuIconName
228                                                                            extension:EXTENSION_MENU_BAR_ICONS
229                                                           resourceFolderName:RESOURCE_MENU_BAR_ICONS];
230         }
231         
232         [menuIcons release];
233         menuIcons = [[AIMenuBarIcons alloc] initWithURL:[NSURL fileURLWithPath:menuIconPath]];
234         
235         [self updateMenuIcons];
238 // Updates the unread count of the status item.
239 - (void)updateUnreadCount
241         int unreadCount = [[adium chatController] unviewedContentCount];
243         // Only show if enabled and greater-than zero; otherwise, set to nil.
244         if (showUnreadCount && unreadCount > 0) {
245                 [statusItem setTitle:[NSString stringWithFormat:@"%i", unreadCount]];
246         } else {
247                 [statusItem setTitle:nil];
248         }
251 // Flashes unviewed content.
252 - (void)updateUnviewedContentFlash:(NSTimer *)timer
254         // Invert our current setting
255         currentlyIgnoringUnviewed = !currentlyIgnoringUnviewed;
256         // Update our current menu icon
257         [self updateMenuIcons];
260 - (void)invalidateTimers
262         currentlyIgnoringUnviewed = NO;
263         [unviewedContentFlash invalidate];
264         [unviewedContentFlash release]; unviewedContentFlash = nil;
267 #define IMAGE_TYPE_CONTENT              @"Content"
268 #define IMAGE_TYPE_AWAY                 @"Away"
269 #define IMAGE_TYPE_IDLE                 @"Idle"
270 #define IMAGE_TYPE_INVISIBLE    @"Invisible"
271 #define IMAGE_TYPE_OFFLINE              @"Offline"
272 #define IMAGE_TYPE_ONLINE               @"Online"
274 - (void)updateMenuIcons
276         NSImage                 *badge = nil;
277         BOOL                    anyAccountHasStatusMessage;
278         NSString                *imageName;
279         NSEnumerator    *enumerator;
280         AIAccount               *account;
282         // If there's content, set our badge to the "content" icon.
283         if (unviewedContent && !currentlyIgnoringUnviewed) {
284                 if (showBadge) {
285                         badge = [AIStatusIcons statusIconForStatusName:@"content"
286                                                                                                 statusType:AIAvailableStatusType
287                                                                                               iconType:AIStatusIconList
288                                                                                                  direction:AIIconNormal];
289                 }
290                 
291                 imageName = IMAGE_TYPE_CONTENT;
292         } else {
293                 // Get the correct icon for our current state.
294                 switch([[[adium statusController] activeStatusState] statusType]) {
295                         case AIAwayStatusType:
296                                 if (showBadge) {
297                                         badge = [[[adium statusController] activeStatusState] icon];
298                                 }
299                                 
300                                 imageName = IMAGE_TYPE_AWAY;
301                                 break;
302                         
303                         case AIInvisibleStatusType:
304                                 if (showBadge) {
305                                         badge = [[[adium statusController] activeStatusState] icon];
306                                 }
307                                 
308                                 imageName = IMAGE_TYPE_INVISIBLE;
309                                 break;
310                                 
311                         case AIOfflineStatusType:
312                                 imageName = IMAGE_TYPE_OFFLINE;
313                                 break;
314                                 
315                         default:
316                                 // Online badging order of presence: idle > reconnecting account > status message
317                                 
318                                 // Assuming we're using an online image unless proven otherwise
319                                 imageName = IMAGE_TYPE_ONLINE;
321                                 // Check idle here, since it has less precedence than offline, invisible, or away.
322                                 anyAccountHasStatusMessage = NO;
323                                 enumerator = [[[adium accountController] accounts] objectEnumerator];
325                                 // Check each account for IdleSince, a StatusState status message, or "Waiting to Reconnect"
326                                 while ((account = [enumerator nextObject])) {
327                                         if ([account online] && [account statusObjectForKey:@"IdleSince"]) {
328                                                 if (showBadge) {
329                                                         badge = [AIStatusIcons statusIconForStatusName:@"Idle"
330                                                                                                                                 statusType:AIAvailableStatusType
331                                                                                                                                   iconType:AIStatusIconList
332                                                                                                                                  direction:AIIconNormal];
333                                                 }
334                                                 
335                                                 imageName = IMAGE_TYPE_IDLE;
336                                                 
337                                                 // We don't need to check anymore; idle has high precedence than offline or available with a status message.
338                                                 break;
339                                         } else if (showBadge &&
340                                                            ([account statusObjectForKey:@"Waiting to Reconnect"] ||
341                                                                 [[account statusObjectForKey:@"Connecting"] boolValue])) {
342                                                 badge = [AIStatusIcons statusIconForStatusName:@"Offline"
343                                                                                                                         statusType:AIOfflineStatusType
344                                                                                                                           iconType:AIStatusIconList
345                                                                                                                          direction:AIIconNormal];
346                                         } else if ([account online] && [[account statusObjectForKey:@"StatusState"] statusMessage]) {
347                                                 anyAccountHasStatusMessage = YES;
348                                         }
349                                 }
350                                 
351                                 // If we already haven't chosen a badge (for example, offline for a reconnecting account)
352                                 // and we have a status message set on any online account, use an online badge
353                                 if (showBadge && !badge && anyAccountHasStatusMessage) {
354                                         badge = [[[adium statusController] activeStatusState] icon];
355                                 }
357                                 break;
358                 }
359         }
360         
361         NSImage *menuIcon = [menuIcons imageOfType:imageName alternate:NO];
362         NSImage *alternateMenuIcon = [menuIcons imageOfType:imageName alternate:YES];
363         
364         // Set our icon.
365         [statusItem setImage:[self badgeDuck:menuIcon withImage:badge]];
366         // Badge the highlight image and set it.
367         [statusItem setAlternateImage:[self badgeDuck:alternateMenuIcon withImage:badge]];
368         // Update our unread count.
369         if (showUnreadCount) {
370                 [self updateUnreadCount];
371         }
374 - (NSImage *)badgeDuck:(NSImage *)duckImage withImage:(NSImage *)badgeImage 
376         NSImage *image = duckImage;
377         
378         if (badgeImage) {
379                 image = [[duckImage copy] autorelease];
380                 
381                 [image lockFocus];
382                 
383                 NSRect srcRect = { NSZeroPoint, [badgeImage size] };
384                 //Draw in the lower-right quadrant.
385                 NSRect destRect = {
386                         { .x = srcRect.size.width, .y = 0.0 },
387                         [duckImage size]
388                 };
389                 destRect.size.width  *= 0.5;
390                 destRect.size.height *= 0.5;
391                 
392                 //If the badge is bigger than that portion, resize proportionally. Otherwise, leave it alone and adjust the destination origin appropriately.
393                 if ((srcRect.size.width > destRect.size.width) || (srcRect.size.height > destRect.size.height)) {
394                         //Resize the dest rect.
395                         float scale;
396                         if (srcRect.size.width > srcRect.size.height) {
397                                 scale = destRect.size.width  / srcRect.size.width;
398                         } else {
399                                 scale = destRect.size.height / srcRect.size.height;
400                         }
401                         
402                         destRect.size.width  = srcRect.size.width  * scale;
403                         destRect.size.height = srcRect.size.height * scale;
404                         
405                         //Make sure we scale in a pretty manner.
406                         [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
407                 }
408                 
409                 //Move the drawing origin.
410                 destRect.origin.x = [duckImage size].width - destRect.size.width;
411                 
412                 [badgeImage drawInRect:destRect
413                                           fromRect:srcRect
414                                          operation:NSCompositeSourceOver
415                                           fraction:1.0];
416                 [image unlockFocus];
417         }
418         
419         return image;
422 //Account Menu --------------------------------------------------------
423 #pragma mark Account Menu
424 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didRebuildMenuItems:(NSArray *)menuItems {
425         // Going from or to 1 account requires a main menu update
426         if ([accountMenuItemsArray count] == 1 || [menuItems count] == 1)
427                 mainMenuNeedsUpdate = YES;
428         
429         
430         [accountMenuItemsArray release];
431         accountMenuItemsArray = [menuItems retain];
432         
433         //We need to update next time we're clicked
434         accountsMenuNeedsUpdate = YES;
437 - (void)accountMenu:(AIAccountMenu *)inAccountMenu didSelectAccount:(AIAccount *)inAccount {
438         [inAccount toggleOnline];
442 //Status Menu --------------------------------------------------------
443 #pragma mark Status Menu
444 - (void)statusMenu:(AIStatusMenu *)inStatusMenu didRebuildStatusMenuItems:(NSArray *)menuItemArray
446         [stateMenuItemsArray release];
447         stateMenuItemsArray = [menuItemArray retain];
448         
449         //We need to update next time we're clicked
450         mainMenuNeedsUpdate = YES;
453 //Contact Menu --------------------------------------------------------
454 #pragma mark Contact Menu
455 - (void)contactMenu:(AIContactMenu *)inContactMenu didRebuildMenuItems:(NSArray *)menuItems
457         // Going from or to 0 contacts requires a main menu update
458         if ([contactMenuItemsArray count] == 0 || [menuItems count] == 0)
459                 mainMenuNeedsUpdate = YES;
461         [contactMenuItemsArray release];
462         contactMenuItemsArray = [menuItems retain];
463         
464         // Update the next time we're clicked.
465         contactsMenuNeedsUpdate = YES;
468 - (void)contactMenu:(AIContactMenu *)inContactMenu didSelectContact:(AIListContact *)inContact
470         [[adium interfaceController] setActiveChat:[[adium chatController] openChatWithContact:inContact
471                                                                                                                                                 onPreferredAccount:YES]];
472         [self activateAdium:nil];
475 - (BOOL)contactMenu:(AIContactMenu *)inContactMenu shouldIncludeContact:(AIListContact *)inContact
477         // Show this contact if we're showing offline contacts or if this contact is online.
478         return [inContact visible];
481 - (BOOL)contactMenuShouldDisplayGroupHeaders:(AIContactMenu *)inContactMenu
483         return showContactGroups;
486 - (BOOL)contactMenuShouldUseDisplayName:(AIContactMenu *)inContactMenu
488         return YES;
491 - (BOOL)contactMenuShouldUseUserIcon:(AIContactMenu *)inContactMenu
493         return YES;
496 - (BOOL)contactMenuShouldSetTooltip:(AIContactMenu *)inContactMenu
498         return YES;
501 //List Object Observer -------------------------------------------------
502 #pragma mark List Object Observer
503 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
505         if ([inObject isKindOfClass:[AIAccount class]]) {
506                 if ([inModifiedKeys containsObject:@"Connecting"] ||
507                         [inModifiedKeys containsObject:@"Waiting to Reconnect"]) {
508                         [self updateMenuIcons];
509                 }
510         }
511         
512         return nil;
515 //Chat Observer --------------------------------------------------------
516 #pragma mark Chat Observer
518 - (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
520         [self updateOpenChats];
521         
522         // We didn't modify anything; return nil.
523         return nil;
526 - (void)updateOpenChats
528         [self retain];
529         
530         int unviewedContentCount = [[adium chatController] unviewedContentCount];
532         // Update our open chats
533         [openChatsArray release];
534         openChatsArray = [[[adium interfaceController] openChats] retain];
535         
536         // We think there's unviewed content, but there's not.
537         if (unviewedContent && unviewedContentCount == 0) {
538                 // Invalidate and release the unviewed content flash timer
539                 [unviewedContentFlash invalidate];
540                 [unviewedContentFlash release]; unviewedContentFlash = nil;
541                 currentlyIgnoringUnviewed = NO;
542                 
543                 // Update unviewed content
544                 unviewedContent = NO;
545                 
546                 // Update our menu icons
547                 [self updateMenuIcons];
548         // We think there's no unviewed content, and there is.
549         } else if (!unviewedContent && unviewedContentCount > 0) {
550                 // If this particular Xtra wants us to flash unviewed content, start the timer up
551                 if (flashUnviewed) {
552                         currentlyIgnoringUnviewed = NO;
553                         unviewedContentFlash = [[NSTimer scheduledTimerWithTimeInterval:1.0
554                                                                                                                                          target:self
555                                                                                                                                    selector:@selector(updateUnviewedContentFlash:)
556                                                                                                                                    userInfo:nil
557                                                                                                                                         repeats:YES] retain];
558                 }
559                 
560                 // Update unviewed content
561                 unviewedContent = YES;
562                 
563                 // Update our menu icons
564                 [self updateMenuIcons];
565         // If we already know there's unviewed content, just update the count.
566         } else if (unviewedContent && unviewedContentCount > 0) {
567                 [self updateUnreadCount];
568         }
570         mainMenuNeedsUpdate = YES;      
571         
572         [self release];
575 //Menu Delegates/Actions --------------------------------------------------------
576 #pragma mark Menu Delegates/Actions
577 - (void)menuNeedsUpdate:(NSMenu *)menu
579         // Main menu if not holding option key
580         if (![NSEvent optionKey] && (menu == mainMenu && mainMenuNeedsUpdate)) {
581                 NSEnumerator    *enumerator;
582                 NSMenuItem      *menuItem;
583                 
584                 //Clear out all the items, start from scratch
585                 [menu removeAllItems];
587                 //Add the state menu items
588                 enumerator = [stateMenuItemsArray objectEnumerator];
589                 menuItem = nil;
590                 while ((menuItem = [enumerator nextObject])) {
591                         [menu addItem:menuItem];
592                         
593                         //Validate the menu items as they are added since they weren't previously validated when the menu was clicked
594                         if ([[menuItem target] respondsToSelector:@selector(validateMenuItem:)]) {
595                                 [[menuItem target] validateMenuItem:menuItem];
596                         }
597                 }
599                 [menu addItem:[NSMenuItem separatorItem]];
600                 
601                 // If there's more than one account, show the accounts menu
602                 if ([accountMenuItemsArray count] > 1) {
603                         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Accounts",nil)
604                                                                                                                                                          target:self
605                                                                                                                                                         action:nil
606                                                                                                                                           keyEquivalent:@""];
607                         
608                         [menuItem setSubmenu:mainAccountsMenu];
609                         [menu addItem:menuItem];
610                         [menuItem release];
611                 }
612                 
613                 // Show the contacts menu if we have any contacts to display
614                 if ([contactMenuItemsArray count] > 0) {
615                         // Add contacts
616                         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Contacts",nil)
617                                                                                                                                                          target:self
618                                                                                                                                                         action:nil
619                                                                                                                                           keyEquivalent:@""];
621                         [menuItem setSubmenu:mainContactsMenu];
622                         [menu addItem:menuItem];
623                         [menuItem release];
624                 } else {
625                         [menu addItemWithTitle:[AILocalizedString(@"Contact List", nil) stringByAppendingEllipsis]
626                                                         target:self
627                                                         action:@selector(activateContactList:)
628                                          keyEquivalent:@""];
629                 }
630                 
631                 //If there exist any open chats, add them
632                 if ([openChatsArray count] > 0) {
633                         AIChat          *chat = nil;
635                         enumerator = [openChatsArray objectEnumerator];
636                         
637                         //Add a seperator
638                         [menu addItem:[NSMenuItem separatorItem]];
639                         
640                         //Create and add the menu items
641                         while ((chat = [enumerator nextObject])) {
642                                 NSImage *image = nil;
643                                 //Create a menu item from the chat
644                                 menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[chat displayName]
645                                                                                                                                                                 target:self
646                                                                                                                                                                 action:@selector(switchToChat:)
647                                                                                                                                                  keyEquivalent:@""];
648                                 //Set the represented object
649                                 [menuItem setRepresentedObject:chat];
650                                 
651                                 //Set the image
652                                 
653                                 //If there is a chat status image, use that
654                                 image = [AIStatusIcons statusIconForChat:chat type:AIStatusIconMenu direction:AIIconNormal];
655                                 //Otherwise use the chat's -chatMenuImage
656                                 if (!image) {
657                                         image = [chat chatMenuImage];
658                                 }
659                                 
660                                 [menuItem setImage:image];
661                                 
662                                 //Add it to the menu
663                                 [menu addItem:menuItem];
664                                 [menuItem release];
665                         }
666                 }
667                 
668                 //Only update next time if we need to
669                 mainMenuNeedsUpdate = NO;
670         // Contacts menu - or, override the main menu with option held down
671         } else if (((menu == mainMenu) && [NSEvent optionKey]) || ((menu == mainContactsMenu) && contactsMenuNeedsUpdate)) {
672                 NSEnumerator    *enumerator = [contactMenuItemsArray objectEnumerator];
673                 NSMenuItem      *menuItem;
674                 
675                 // Remove previous menu items.
676                 [menu removeAllItems];
677                 
678                 // If this is the contact menu being pushed into the main one, force an update next time
679                 if ([NSEvent optionKey]) {
680                         // Remove contact menu items from the old menu
681                         [mainContactsMenu removeAllItems];
682                         // Have both menus update next time
683                         mainMenuNeedsUpdate = YES;
684                         contactsMenuNeedsUpdate = YES;
685                 } else {
686                         contactsMenuNeedsUpdate = NO;
687                 }
688                 
689                 [menu addItemWithTitle:[AILocalizedString(@"Contact List", nil) stringByAppendingEllipsis]
690                                                 target:self
691                                                 action:@selector(activateContactList:)
692                                  keyEquivalent:@""];
693                 
694                 if ([contactMenuItemsArray count] > 0)
695                         [menu addItem:[NSMenuItem separatorItem]];
696                 
697                 while ((menuItem = [enumerator nextObject])) {
698                         [menu addItem:menuItem];
699                         
700                         //Validate the menu items as they are added since they weren't previously validated when the menu was clicked
701                         if ([[menuItem target] respondsToSelector:@selector(validateMenuItem:)]) {
702                                 [[menuItem target] validateMenuItem:menuItem];
703                         }
704                 }
705         // Accounts menu
706         } else if (menu == mainAccountsMenu && accountsMenuNeedsUpdate) {
707                 NSEnumerator    *enumerator = [accountMenuItemsArray objectEnumerator];
708                 NSMenuItem      *menuItem;
709                 
710                 [menu removeAllItems];
711                 
712                 [menu addItemWithTitle:[AILocalizedString(@"Account List", nil) stringByAppendingEllipsis]
713                                                                         target:self
714                                                                         action:@selector(activateAccountList:)
715                                                          keyEquivalent:@""];
716                 
717                 [menu addItem:[NSMenuItem separatorItem]];
718                 
719                 //Add the account menu items
720                 enumerator = [accountMenuItemsArray objectEnumerator];
721                 while ((menuItem = [enumerator nextObject])) {
722                         NSMenu  *submenu;
723                         
724                         [menu addItem:menuItem];
725                         
726                         //Validate the menu items as they are added since they weren't previously validated when the menu was clicked
727                         if ([[menuItem target] respondsToSelector:@selector(validateMenuItem:)]) {
728                                 [[menuItem target] validateMenuItem:menuItem];
729                         }
730                         
731                         submenu = [menuItem submenu];
732                         if (submenu) {
733                                 NSEnumerator    *submenuEnumerator = [[submenu itemArray] objectEnumerator];
734                                 NSMenuItem              *submenuItem;
735                                 while ((submenuItem = [submenuEnumerator nextObject])) {
736                                         //Validate the submenu items as they are added since they weren't previously validated when the menu was clicked
737                                         if ([[submenuItem target] respondsToSelector:@selector(validateMenuItem:)]) {
738                                                 [[submenuItem target] validateMenuItem:submenuItem];
739                                         }
740                                 }
741                         }
742                 }
743                 
744                 accountsMenuNeedsUpdate = NO;
745         }
748 - (void)switchToChat:(id)sender
750         [[adium interfaceController] setActiveChat:[sender representedObject]];
751         [self activateAdium:nil];
754 - (void)activateContactList:(id)sender
756         [[adium interfaceController] showContactList:nil];
757         [self activateAdium:nil];
760 - (void)activateAccountList:(id)sender
762         [[adium preferenceController] openPreferencesToCategoryWithIdentifier:@"Accounts"];
763         [self activateAdium:nil];
766 - (void)activateAdium:(id)sender
768         if (![NSApp isActive]) {
769                 [NSApp activateIgnoringOtherApps:YES];
770                 [NSApp arrangeInFront:nil];
771         }
774 #pragma mark Preferences Observer
775 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
776                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
778         if ([group isEqualToString:PREF_GROUP_CONTACT_LIST_DISPLAY]) {
779                 showContactGroups = ![[prefDict objectForKey:KEY_HIDE_CONTACT_LIST_GROUPS] boolValue];
780                 [contactMenu rebuildMenu];
781         }
782         
783         if ([group isEqualToString:PREF_GROUP_STATUS_MENU_ITEM]) {
784                 showUnreadCount = [[prefDict objectForKey:KEY_STATUS_MENU_ITEM_COUNT] boolValue];
785                 showBadge = [[prefDict objectForKey:KEY_STATUS_MENU_ITEM_BADGE] boolValue];
786                 flashUnviewed = [[prefDict objectForKey:KEY_STATUS_MENU_ITEM_FLASH] boolValue];
787                 
788                 [self updateMenuIcons];
789                 [self updateUnreadCount];
790         }
793 #pragma mark -
795 - (void)menuBarIconsDidChange:(NSNotification *)notification
797         [self updateMenuIconsBundle];
799 @end